package net.gazhi.delonix.core.validation;

import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.metadata.ConstraintDescriptor;

import org.springframework.beans.NotReadablePropertyException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean;

/**
 * 改写原有 Validator 生成的 FieldError，把 value 暴露为 第 {0} 个 message 参数
 * 
 * @author Jeffrey Lin
 *
 */
public class ExposeValueToArgsValidator extends OptionalValidatorFactoryBean {

	protected void processConstraintViolations(Set<ConstraintViolation<Object>> violations, Errors errors) {
		for (ConstraintViolation<Object> violation : violations) {
			String field = violation.getPropertyPath().toString();
			FieldError fieldError = errors.getFieldError(field);
			if (fieldError == null || !fieldError.isBindingFailure()) {
				try {
					ConstraintDescriptor<?> cd = violation.getConstraintDescriptor();
					String errorCode = cd.getAnnotation().annotationType().getSimpleName();
					Object[] errorArgs = getArgumentsForConstraint(errors.getObjectName(), field, cd);
					if (errors instanceof BindingResult) {
						// Can do custom FieldError registration with invalid
						// value from ConstraintViolation,
						// as necessary for Hibernate Validator compatibility
						// (non-indexed set path in field)
						BindingResult bindingResult = (BindingResult) errors;
						String nestedField = bindingResult.getNestedPath() + field;
						if ("".equals(nestedField)) {
							String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
							bindingResult.addError(new ObjectError(errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()));
						} else {
							Object invalidValue = violation.getInvalidValue();
							if (!"".equals(field) && (invalidValue == violation.getLeafBean() || (field.contains(".") && !field.contains("[]")))) {
								// Possibly a bean constraint with property
								// path: retrieve the actual property value.
								// However, explicitly avoid this for
								// "address[]" style paths that we can't handle.
								invalidValue = bindingResult.getRawFieldValue(field);
							}

							// { 改写开始 By Jeffrey Lin
							Object[] revisedArgs = new Object[errorArgs.length + 1];
							revisedArgs[0] = invalidValue;
							for (int i = 0; i < errorArgs.length; i++) {
								revisedArgs[i + 1] = errorArgs[i];
							}
							// 改写结束 }

							String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field);
							bindingResult.addError(new FieldError(errors.getObjectName(), nestedField, invalidValue, false, errorCodes, revisedArgs, violation.getMessage()));
						}
					} else {
						// got no BindingResult - can only do standard
						// rejectValue call
						// with automatic extraction of the current field value
						errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());
					}
				} catch (NotReadablePropertyException ex) {
					throw new IllegalStateException("JSR-303 validated property '" + field + "' does not have a corresponding accessor for Spring data binding - "
							+ "check your DataBinder's configuration (bean property versus direct field access)", ex);
				}
			}
		}
	}
}
